﻿using AutocompleteMenuNS;
using Microsoft.CSharp.RuntimeBinder;
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace LabyCLI
{
    /// <summary>
    /// Represents the standard input, output, and error display information for the terminal.
    /// </summary>
	public partial class Terminal : Form
    {
        public static Terminal Instance;
		public Parser parser = new Parser();

        private AutocompleteMenu _autoCompleteMenu;
        private RichTextBox _currentWindow;
        private string _userinput;
        private System.Threading.SynchronizationContext _syncContext;

        //Command
        private string _cmd;
        private List<string> _arguments = new List<string>();

        public Terminal()
        {
            Instance = this;

            this.Text = Directory.GetCurrentDirectory(); //TODO: Look at this.

            _syncContext = System.Threading.SynchronizationContext.Current; //TODO temp
        }

        /// <summary>
        /// Sets the currently used AutoCompleteMenu to active.
        /// </summary>
        /// <param name="AutoCompleteMenu"></param>
        protected void SetAutoCompleteMenu(AutocompleteMenu AutoCompleteMenu)
        {
            _autoCompleteMenu = AutoCompleteMenu;
        }

        /// <summary>
        /// Gets the currently active AutoCompleteMenu.
        /// </summary>
        /// <returns></returns>
        public AutocompleteMenu GetAutoCompleteMenu()
        {
            if (_currentWindow == null)
            {
                throw new Exception("Form must set current AutoCompleteMenu.");
            }

            return _autoCompleteMenu;
        }

        /// <summary>
        /// Sets the currently used terminal to active.
        /// </summary>
        /// <param name="RichTextBox"></param>
        protected void SetCurrentTerminal(RichTextBox RichTextBox)
        {
            _currentWindow = RichTextBox;

            //Attaches the eventhandlers AFTER the richTextBox has beet set (also why we dont do it in the constructor)
            _currentWindow.KeyDown += new KeyEventHandler(Window_KeyDown);
        }

        /// <summary>
        /// Gets the currently active terminal.
        /// </summary>
        /// <returns></returns>
        public RichTextBox GetCurrentTerminal()
        {
            if (_currentWindow == null)
			{
                throw new Exception("Form must set current window.");
            }

			return _currentWindow;
        }

        #region Terminal Methods

        #region Append Methods

        /// <summary>
        /// Writes a string to the terminal, followed by a line break.
        /// </summary>
        /// <param name="Message">A string that gets displayed on the terminal.</param>
        public static void WriteLine(string Message)
		{
			Terminal terminal = Instance;
            AppendText(terminal.GetCurrentTerminal(), Environment.NewLine + Message, Properties.Settings.Default.Terminal_CommandTextColor, Properties.Settings.Default.Terminal_CommandBackgroundColor);
            terminal.GetCurrentTerminal().Select(terminal.GetCurrentTerminal().Text.Length - 0, 0);
		}

        /// <summary>
        /// Writes a string to the terminal, with a foreground color, followed by a line break.
        /// </summary>
        /// <param name="Message">A string that gets displayed on the terminal.</param>
        /// <param name="ForeColor">Sets the foreground color of the text.</param>
        public static void WriteLine(string Message, Color ForeColor)
		{
			Terminal terminal = Instance;
            AppendText(terminal.GetCurrentTerminal(), Environment.NewLine + Message, ForeColor, Properties.Settings.Default.Terminal_CommandBackgroundColor);
            terminal.GetCurrentTerminal().Select(terminal.GetCurrentTerminal().Text.Length - 0, 0);
		}

        /// <summary>
        /// Writes a string to the terminal, with a fore- and background color, followed by a line break. 
        /// </summary>
        /// <param name="Message">A string that gets displayed on the terminal.</param>
        /// <param name="ForeColor">Sets the foreground color of the text.</param>
        /// <param name="BackColor">Sets the background color of the text.</param>
        public static void WriteLine(string Message, Color ForeColor, Color BackColor)
        {
            Terminal terminal = Instance;
            AppendText(terminal.GetCurrentTerminal(), Environment.NewLine + Message, ForeColor, BackColor);
            terminal.GetCurrentTerminal().Select(terminal.GetCurrentTerminal().Text.Length - 0, 0);
        }

        /// <summary>
        /// Writes a string to the terminal, with a fore- and background color, containing a fontstyle, followed by a line break. 
        /// </summary>
        /// <param name="Message">A string that gets displayed on the terminal.</param>
        /// <param name="ForeColor">Sets the foreground color of the text.</param>
        /// <param name="BackColor">Sets the background color of the text.</param>
        /// <param name="FontStyle">Sets the fontstyle of the text.</param>
        public static void WriteLine(string Message, Color ForeColor, Color BackColor, FontStyle FontStyle)
        {
            Terminal terminal = Instance;
            AppendText(terminal.GetCurrentTerminal(), Environment.NewLine + Message, ForeColor, BackColor, true, FontStyle);
            terminal.GetCurrentTerminal().Select(terminal.GetCurrentTerminal().Text.Length - 0, 0);
        }

        /// <summary>
        /// Writes a string to the terminal.
        /// </summary>
        /// <param name="Message">A string that gets displayed on the terminal.</param>
        /// <param name="ForeColor">Sets the foreground color of the text.</param>
        public static void Write(string Message)
		{
			Terminal terminal = Instance;
			AppendText(terminal.GetCurrentTerminal(), Message, Properties.Settings.Default.Terminal_CommandTextColor, Properties.Settings.Default.Terminal_CommandBackgroundColor);
			terminal.GetCurrentTerminal().Select(terminal.GetCurrentTerminal().Text.Length - 0, 0);
		}

        /// <summary>
        /// Writes a string to the terminal, with a foreground color.
        /// </summary>
        /// <param name="Message">A string that gets displayed on the terminal.</param>
        /// <param name="ForeColor">Sets the foreground color of the text.</param>
        public static void Write(string Message, Color ForeColor)
		{
			Terminal terminal = Instance;
			AppendText(terminal.GetCurrentTerminal(), Message, ForeColor, Properties.Settings.Default.Terminal_CommandBackgroundColor);
			terminal.GetCurrentTerminal().Select(terminal.GetCurrentTerminal().Text.Length - 0, 0);
		}

        /// <summary>
        /// Writes a string to the terminal, with a fore- and background color. 
        /// </summary>
        /// <param name="Message">A string that gets displayed on the terminal.</param>
        /// <param name="ForeColor">Sets the foreground color of the text.</param>
        /// <param name="BackColor">Sets the background color of the text.</param>
        public static void Write(string Message, Color ForeColor, Color BackColor)
        {
            Terminal terminal = Instance;
            AppendText(terminal.GetCurrentTerminal(), Message, ForeColor, BackColor);
            terminal.GetCurrentTerminal().Select(terminal.GetCurrentTerminal().Text.Length - 0, 0);
        }

        /// <summary>
        /// Writes a string to the terminal, with a fore- and background color, containing a fontstyle. 
        /// </summary>
        /// <param name="Message">A string that gets displayed on the terminal.</param>
        /// <param name="ForeColor">Sets the foreground color of the text.</param>
        /// <param name="BackColor">Sets the background color of the text.</param>
        /// <param name="FontStyle">Sets the fontstyle of the text.</param>
        public static void Write(string Message, Color ForeColor, Color BackColor, FontStyle FontStyle)
        {
            Terminal terminal = Instance;
            AppendText(terminal.GetCurrentTerminal(), Message, ForeColor, BackColor, true, FontStyle);
            terminal.GetCurrentTerminal().Select(terminal.GetCurrentTerminal().Text.Length - 0, 0);
        }

        /// <summary>
		/// Print a editable/adjustable string to the terminal.
		/// </summary>
		/// <param name="Message">A string that gets displayed on the terminal.</param>
		public static void Print(string Message)
        {
            Terminal terminal = Instance;
            AppendText(terminal.GetCurrentTerminal(), Message, Properties.Settings.Default.Terminal_CommandTextColor, Properties.Settings.Default.Terminal_CommandBackgroundColor, false);
            terminal.GetCurrentTerminal().Select(terminal.GetCurrentTerminal().Text.Length - 0, 0);
        }

        public static void Beep(int frequency, int duration)
        {
            Console.Beep(frequency, duration);
        }

        public static void Sleep(int duration)
        {
            System.Threading.Thread.Sleep(duration);
        }

        private static void AppendText(RichTextBox Window, string Message, Color ForeColor, Color BackColor, bool ReadOnly = true, FontStyle FontStyle = FontStyle.Regular)
        {
            int start = Window.TextLength;
            Window.AppendText(Message);
            int end = Window.TextLength;

            //Textbox may transform chars, so (end-start) != text.Length
            Window.Select(start, end - start);
            {
                Window.SelectionFont = new Font(Window.Font, FontStyle);
                Window.SelectionColor = ForeColor;
                Window.SelectionBackColor = BackColor;
                Window.SelectionProtected = ReadOnly;
            }

            Window.SelectionLength = 0; //Removes our selection.
        }

        #endregion

        /// <summary>
        /// Clears the terminal and corresponding text of display information.
        /// </summary>
        public static void Clear ()
        {
            Terminal terminal = Instance;
            terminal._currentWindow.ResetText();
        }

        #endregion

        #region Terminal event handlers

        private void Window_KeyDown(object sender, KeyEventArgs e)
        {
            //Når der bliver trykket på en tast (f.eks. enter).
            if (e.KeyCode == Keys.F3)
            {
                frmSettings frm = new frmSettings();
                frm.ShowDialog();
            }
            else if (e.KeyCode == Keys.Enter)
            {
                int totalLines;
				
                try
                {
                    totalLines = GetCurrentTerminal().Lines.Length;
                    _userinput = GetCurrentTerminal().Lines[totalLines - 1];
                }
                catch
				{
					_userinput = string.Empty;
				}

                if (!string.IsNullOrEmpty(_userinput))
                {
                    parser.Parse(_userinput);

                    _cmd = parser.getCmd();

                    _arguments = parser.getArgs();
                }
                else
                {
                    _cmd = string.Empty;
                }

				try
				{
                    //string executable = "LabyCLI.Commands." + System.Text.RegularExpressions.Regex.Replace(cmd, System.Text.RegularExpressions.Regex.Match(cmd, "(?<=\\.)([\\w]+)", System.Text.RegularExpressions.RegexOptions.RightToLeft).ToString(), "") + "cmd_" + System.Text.RegularExpressions.Regex.Match(cmd, "(?<=\\.)([\\w]+)", System.Text.RegularExpressions.RegexOptions.RightToLeft).ToString();
                    //TODO: Review this, one line, code ^
                    string executable = "LabyCLI.Commands." + "cmd_" + _cmd.ToLower();
					dynamic d = Assembly.GetExecutingAssembly().CreateInstance(executable);

                    if (_arguments.Any())
                    {
                        d.Main(_arguments);
                    }
                    else
                    {
						d.Main();
                    }
				}
				catch (RuntimeBinderException)
				{
                    //TODO: Clean up this code.

                    //Try and run it as a normal cmd command.
                    try
                    {
                        //System.Diagnostics.Process pr = new System.Diagnostics.Process();
                        //System.Diagnostics.ProcessStartInfo processStartInfo = new System.Diagnostics.ProcessStartInfo();
                        //processStartInfo.FileName = Path.GetDirectoryName(Application.ExecutablePath) + "\\Commands\\" + _cmd.ToLower();
                        //processStartInfo.UseShellExecute = false;
                        //processStartInfo.CreateNoWindow = true;
                        //processStartInfo.RedirectStandardOutput = true;


                        // Start the child process.
                        System.Diagnostics.Process p = new System.Diagnostics.Process();
                        // Redirect the output stream of the child process.
                        p.StartInfo.UseShellExecute = false;
                        p.StartInfo.CreateNoWindow = true;
                        p.StartInfo.RedirectStandardOutput = true;
                        p.StartInfo.RedirectStandardError = true;
                        p.StartInfo.FileName = Path.GetDirectoryName(Application.ExecutablePath) + "\\Commands\\" + _cmd.ToLower();

                        if (_arguments.Any())
                        {
                            StringBuilder sb = new StringBuilder();

                            foreach (string arg in _arguments)
                            {
                                sb.Append(arg + " ");
                            }
                            p.StartInfo.Arguments = sb.ToString();
                        }

                        p.Start();
                        p.WaitForExit();

                        ////While this works... It is also painfully slow.
                        //while (!p.StandardOutput.EndOfStream)
                        //{
                        //    string line = p.StandardOutput.ReadLine();
                        //    Terminal.WriteLine(line);
                        //}

                        string output = p.StandardOutput.ReadToEnd();
                        string error = p.StandardError.ReadToEnd();

                        Terminal.Beep(1500, 100);
                        Terminal.Write(output);
                    }
                    catch //The command do not exist.
                    {
                        WriteLine("Command was not found and couldn't be handled." + Environment.NewLine, Properties.Settings.Default.Terminal_ErrorForeColor);
                    }
                }
				catch (Exception ex)
				{
                    Terminal.Beep(500, 1000);
					WriteLine(Application.ProductName + " failed the given task. " + ex + Environment.NewLine, Color.White, Color.Red);
				}
            }
            else if (e.KeyCode == Keys.Up || e.KeyCode == Keys.Down)
            {
                e.Handled = true;
            }
            else if (e.KeyCode == Keys.Left)
            {
                int i = 0;
                
                if (GetCurrentTerminal().TextLength > 0)
                {
                    int index;
                    index = GetCurrentTerminal().Lines.Length - 1;

                    i = GetCurrentTerminal().TextLength - GetCurrentTerminal().Lines[index].Length;
                }

                if (GetCurrentTerminal().SelectionStart == i)
                {
                    e.Handled = true;
                }
            }
            else if (e.KeyCode == Keys.Right)
            {
                if (GetCurrentTerminal().SelectionStart == GetCurrentTerminal().TextLength)
                {
                    e.Handled = true;
                }
            }
        }

        #endregion
    }
}
